\chapter{模式与模式匹配}
模式（Patterns）是 Rust 中特殊的语法，它用来匹配类型中的结构，无论类型是简单还是复杂。结合使用模式和 match 表达式以及其他结构可以对程序控制流有更好的支配。模式由如下一些内容组合而成：
\begin{itemize}
    \item 字面值
\item 解构的数组、枚举、结构体或者元组
\item 变量
\item 通配符
\item 占位符
\end{itemize}
一些模式的例子包括x, (a, 3) 和 Some(Color::Red)。在模式为有效的上下文中，这些部分描述了数据的形状。接着可以用其匹配值来决定程序是否拥有正确的数据来运行特定部分的代码。我们通过将一些值与模式相比较来使用它。如果模式匹配这些值，我们对值部分进行相应处理。回忆一下第六章讨论 match 表达式时像硬币分类器那样使用模式。如果数据符合这个形状，就可以使用这些命名的片段。如果不符合，与该模式相关的代码则不会运行。

本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置，refutable 与 irrefutable 模式的区别，和你可能会见到的不同类型的模式语法。在最后，你将会看到如何使用模式创建强大而简洁的代码。
\section{所有可能会用到模式的位置}
模式出现在 Rust 的很多地方。你已经在不经意间使用了很多模式！本部分是一个所有有效模式位置的参考。
\subsection{match 分支}
如第六章所讨论的，一个模式常用的位置是 match 表达式的分支。在形式上 match 表达式由 match 关键字、用于匹配的值和一个或多个分支构成，这些分支包含一个模式和在值匹配分支的模式时运行的表达式：
    \begin{rust}
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}
    \end{rust}
例如这是一个来自示例 6-5 中匹配变量 x 中 Option<i32> 值的 match 表达式：
\begin{rust}
match x {
    None => None,
    Some(i) => Some(i + 1),
}
\end{rust}
这个 match 表达式中的模式为每个箭头左边的 None 和 Some(i)。match 表达式必须是 \textbf{穷尽（exhaustive）}的，意为 match 表达式所有可能的值都必须被考虑到。一个确保覆盖每个可能值的方法是在最后一个分支捕获所有的模式：比如，一个匹配任何值的名称永远也不会失败，因此可以覆盖所有匹配剩下的情况。有一个特定的模式 \_ 可以匹配所有情况，不过它从不绑定任何变量。这在例如希望忽略任何未指定值的情况很有用。本章之后的 “忽略模式中的值” 部分会详细介绍 \_ 模式的更多细节。
\subsection{if let 条件表达式}
第六章讨论过了 if let 表达式，以及在只关心匹配情况下的 match 语句简写的。if let 可以对应一个可选的带有代码的 else ，在 if let 中的模式不匹配时运行。
\coderef{code:gracefull_for_match} 展示了也可以组合并匹配 if let、else if 和 else if let 表达式。这相比 match 表达式一次只能将一个值与模式比较提供了更多灵活性。并且 Rust 并不要求一系列 if let、else if、else if let 分支的条件相互关联。
\coderef{code:gracefull_for_match} 中的代码展示了一系列针对不同条件的检查来决定背景颜色应该是什么。为了达到这个例子的目的，我们创建了硬编码值的变量，在真实程序中则可能由询问用户获得。
\begin{listing}[H]
    \centering
    \caption{结合 if let、else if、else if let 以及 else}
    \begin{rust}
fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {color}, as the background");
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}
    \end{rust}
    \label{code:gracefull_for_match}
\end{listing}
如果用户指定了中意的颜色，将使用其作为背景颜色。如果没有指定中意的颜色且今天是星期二，背景颜色将是绿色。如果用户指定了他们的年龄字符串并能够成功将其解析为数字的话，我们将根据这个数字使用紫色或者橙色。最后，如果没有一个条件符合，背景颜色将是蓝色。这个条件结构允许我们支持复杂的需求。使用这里硬编码的值，例子会打印出 Using purple as the background color。注意 if let 也可以像 match 分支那样引入覆盖变量：\rustinline{if let Ok(age) = age} 引入了一个新的覆盖变量 age，它包含 Ok 成员中的值。这意味着 if age > 30 条件需要位于这个代码块内部；不能将两个条件组合为 \rustinline{if let Ok(age) = age && age > 30}，因为我们希望与 30 进行比较的被覆盖的 age 直到大括号开始的新作用域才是有效的。if let 表达式的缺点在于其穷尽性没有为编译器所检查，而 match 表达式则检查了。如果去掉最后的 else 块而遗漏处理一些情况，编译器也不会警告这类可能的逻辑错误。
\subsection{while let 条件循环}
一个与 if let 结构类似的是 while let 条件循环，它允许只要模式匹配就一直进行 while 循环。\coderef{code:while_for_some_with_pop} 展示了一个使用 while let 的例子，它使用 vector 作为栈并以先进后出的方式打印出 vector 中的值：
\begin{listing}[H]
    \centering
    \caption{使用 while let 循环只要 stack.pop() 返回 Some 就打印出其值}
    \begin{rust}
    let mut stack = Vec::new();

    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(top) = stack.pop() {
        println!("{}", top);
    }
    \end{rust}
    \label{code:while_for_some_with_pop}
\end{listing}
这个例子会打印出 3、2 接着是 1。pop 方法取出 vector 的最后一个元素并返回 Some(value)。如果 vector 是空的，它返回 None。while 循环只要 pop 返回 Some 就会一直运行其块中的代码。一旦其返回 None，while 循环停止。我们可以使用 while let 来弹出栈中的每一个元素。
\subsection{for 循环}
在 for 循环中，模式是 for 关键字直接跟随的值，正如 for x in y 中的 x。\coderef{code:for_to_tuple_with_match} 中展示了如何使用 for 循环来解构，或拆开一个元组作为 for 循环的一部分：
\begin{listing}[H]
    \centering
    \caption{在 for 循环中使用模式来解构元组}
    \begin{rust}
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{} is at index {}", value, index);
    }
    \end{rust}
    \label{code:for_to_tuple_with_match}
\end{listing}
\coderef{code:for_to_tuple_with_match} 的代码会打印出：
\begin{bash}
$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
\end{bash}
这里使用 enumerate 方法适配一个迭代器来产生一个值和其在迭代器中的索引，它们位于一个元组中。第一个产生的值是元组 (0, 'a')。当这个值匹配模式 (index, value)，index 将会是 0 而 value 将会是 'a'，并打印出第一行输出。

\subsection{let 语句}\label{sec:let_part}
在本章之前，我们只明确的讨论过通过 match 和 if let 使用模式，不过事实上也在别的地方使用过模式，包括 let 语句。例如，考虑一下这个直白的 let 变量赋值：
\begin{rust}
let x = 5;
\end{rust}
不过你可能没有发觉，每一次像这样使用 let 语句就是在使用模式！let 语句更为正式的样子如下：
\begin{rust}
let PATTERN = EXPRESSION;
\end{rust}
像 \rustinline{let x = 5;} 这样的语句中变量名位于 PATTERN 位置，变量名不过是形式特别朴素的模式。我们将表达式与模式比较，并为任何找到的名称赋值。所以例如 \rustinline{let x = 5;} 的情况，x 是一个代表 “将匹配到的值绑定到变量 x” 的模式。同时因为名称 x 是整个模式，这个模式实际上等于 “将任何值绑定到变量 x，不管值是什么”。为了更清楚的理解 let 的模式匹配方面的内容，考虑\coderef{code:unpack_param_with_tuple} 中使用 let 和模式解构一个元组：
\begin{listing}[H]
    \centering
    \caption{使用模式解构元组并一次创建三个变量}
    \begin{rust}
    let (x, y, z) = (1, 2, 3);
    \end{rust}
    \label{code:unpack_param_with_tuple}
\end{listing}
这里将一个元组与模式匹配。Rust 会比较值 (1, 2, 3) 与模式 (x, y, z) 并发现此值匹配这个模式。在这个例子中，将会把 1 绑定到 x，2 绑定到 y 并将 3 绑定到 z。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。如果模式中元素的数量不匹配元组中元素的数量，则整个类型不匹配，并会得到一个编译时错误。例如，\coderef{code:unpack_param_lead_to_error} 展示了尝试用两个变量解构三个元素的元组，这是不行的：
\begin{listing}[H]
    \centering
    \caption{一个错误的模式结构，其中变量的数量不符合元组中元素的数量}
    \begin{rust}
    let (x, y) = (1, 2, 3);
    \end{rust}
    \label{code:unpack_param_lead_to_error}
\end{listing}
尝试编译这段代码会给出如下类型错误：
\begin{bash}
$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
  |         |
  |         expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` due to previous error
\end{bash}
为了修复这个错误，可以使用 \_ 或 .. 来忽略元组中一个或多个值，如 “忽略模式中的值” 部分所示。如果问题是模式中有太多的变量，则解决方法是通过去掉变量使得变量数与元组中元素数相等。
\subsection{函数参数}
函数参数也可以是模式。\coderef{code:declare_foo} 中的代码声明了一个叫做 foo 的函数，它获取一个 i32 类型的参数 x，现在这看起来应该很熟悉：
\begin{listing}[H]
    \centering
    \caption{在参数中使用模式的函数签名}
    \begin{rust}
fn foo(x: i32) {
    // code goes here
}
    \end{rust}
    \label{code:declare_foo}
\end{listing}
x 部分就是一个模式！类似于之前对 let 所做的，可以在函数参数中匹配元组。\coderef{code:split_tuple_for_func} 将传递给函数的元组拆分为值：
\begin{listing}[H]
    \centering
    \caption{一个在参数中解构元组的函数}
    \begin{rust}
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}
    \end{rust}
    \label{code:split_tuple_for_func}
\end{listing}
这会打印出 Current location: (3, 5)。值 \rustinline{&(3, 5)} 会匹配模式 \rustinline{&(x, y)}，如此 x 得到了值 3，而 y得到了值 5。因为如第十三章所讲闭包类似于函数，也可以在闭包参数列表中使用模式。现在我们见过了很多使用模式的方式了，不过模式在每个使用它的地方并不以相同的方式工作；在一些地方，模式必须是 \textbf{irrefutable} 的，意味着它们必须匹配所提供的任何值。在另一些情况，它们则可以是 \textbf{refutable} 的。接下来让我们讨论这两个概念。
\section{Refutability（可反驳性）: 模式是否会匹配失效}
模式有两种形式：\textbf{refutable（可反驳的）}和 \textbf{irrefutable（不可反驳的）}。能匹配任何传递的可能值的模式被称为是 不可反驳的（irrefutable）。一个例子就是 \rustinline{let x = 5;} 语句中的 x，因为 x 可以匹配任何值所以不可能会失败。对某些可能的值进行匹配会失败的模式被称为是 可反驳的（refutable）。一个这样的例子便是 \rustinline{if let Some(x) = a_value} 表达式中的 Some(x)；如果变量 a\_value 中的值是 None 而不是 Some，那么 Some(x) 模式不能匹配。

函数参数、let 语句和 for 循环只能接受不可反驳的模式，因为当值不匹配时，程序无法进行有意义的操作。if let 和 while let 表达式可以接受可反驳和不可反驳的模式，但编译器会对不可反驳的模式发出警告，因为根据定义它们旨在处理可能的失败：条件表达式的功能在于它能够根据成功或失败来执行不同的操作。通常我们无需担心可反驳和不可反驳模式的区别，不过确实需要熟悉可反驳性的概念，这样当在错误信息中看到时就知道如何应对。遇到这些情况，根据代码行为的意图，需要修改模式或者使用模式的结构。让我们看看一个尝试在 Rust 要求不可反驳模式的地方使用可反驳模式以及相反情况的例子。在\coderef{code:let_some_with_value} 中，有一个 let 语句，不过模式被指定为可反驳模式 Some(x)。如你所见，这不能编译：
\begin{listing}[H]
    \centering
    \caption{尝试在 let 中使用可反驳模式}
    \begin{rust}
    let Some(x) = some_option_value;
    \end{rust}
    \label{code:let_some_with_value}
\end{listing}
如果 some\_option\_value 的值是 None，其不会成功匹配模式 Some(x)，表明这个模式是可反驳的。然而，因为 let 对于 None 匹配不能产生任何合法的代码，所以 let 语句只能接受不可反驳模式。Rust 会在编译时抱怨我们尝试在要求不可反驳模式的地方使用可反驳模式：
\begin{bash}
$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding: `None` not covered
 --> src/main.rs:3:9
  |
3 |     let Some(x) = some_option_value;
  |         ^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
note: `Option<i32>` defined here
  = note: the matched value is of type `Option<i32>`
help: you might want to use `if let` to ignore the variant that isn't matched
  |
3 |     let x = if let Some(x) = some_option_value { x } else { todo!() };
  |     ++++++++++                                 ++++++++++++++++++++++

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` due to previous error
\end{bash}
因为我们没有覆盖（也不可能覆盖！）到模式 Some(x) 的每一个可能的值，所以 Rust 会合理地抗议。为了修复在需要不可反驳模式的地方使用可反驳模式的情况，可以修改使用模式的代码：不同于使用 let，可以使用 if let。如此，如果模式不匹配，大括号中的代码将被忽略，其余代码保持有效。\coderef{code:let_some_with_option} 展示了如何修复\coderef{code:let_some_with_value} 中的代码。
\begin{listing}[H]
    \centering
    \caption{使用 if let 和一个带有可反驳模式的代码块来代替 let}
    \begin{rust}
    if let Some(x) = some_option_value {
        println!("{}", x);
    }
    \end{rust}
    \label{code:let_some_with_option}
\end{listing}
我们给了代码一个得以继续的出路！虽然我们没办法在避免产生错误的情况下使用不可反驳模式，但这段使用可反驳模式的代码是完全有效的。如果为 if let 提供了一个总是会匹配的模式，比如\coderef{code:use_if_let_with_value} 中的 x，编译器会给出一个警告：
\begin{listing}[H]
    \centering
    \caption{尝试把不可反驳模式用到 if let 上}
    \begin{rust}
    if let x = 5 {
        println!("{}", x);
    };
    \end{rust}
    \label{code:use_if_let_with_value}
\end{listing}
Rust 会抱怨将不可反驳模式用于 if let 是没有意义的：
\begin{bash}
$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
 --> src/main.rs:2:8
  |
2 |     if let x = 5 {
  |        ^^^^^^^^^
  |
  = note: `#[warn(irrefutable_let_patterns)]` on by default
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`

warning: `patterns` (bin "patterns") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`
5
\end{bash}
基于此，match匹配分支必须使用可反驳模式，除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。Rust 允许我们在只有一个匹配分支的match中使用不可反驳模式，不过这么做不是特别有用，并可以被更简单的 let 语句替代。目前我们已经讨论了所有可以使用模式的地方，以及可反驳模式与不可反驳模式的区别，下面让我们一起去把可以用来创建模式的语法过目一遍吧。
\section{所有的模式语法}
\subsection{匹配字面值}
如第六章所示，可以直接匹配字面值模式。如下代码给出了一些例子：
    \begin{rust}
    let x = 1;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
    \end{rust}
这段代码会打印 one 因为 x 的值是 1。如果希望代码获得特定的具体值，则该语法很有用。
\subsection{匹配命名变量}
命名变量是匹配任何值的不可反驳模式，这在之前已经使用过数次。然而当其用于 match 表达式时情况会有些复杂。因为 match 会开始一个新作用域，match 表达式中作为模式的一部分声明的变量会覆盖 match 结构之外的同名变量，与所有变量一样。在\coderef{code:conver_value_use_new_branch} 中，声明了一个值为 Some(5) 的变量 x 和一个值为 10 的变量 y。接着在值 x 上创建了一个 match 表达式。观察匹配分支中的模式和结尾的 println!，并在运行此代码或进一步阅读之前推断这段代码会打印什么。
\begin{listing}[H]
    \centering
    \caption{一个 match 语句其中一个分支引入了覆盖变量 y}
    \begin{rust}
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {y}"),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {y}", x);
    \end{rust}
    \label{code:conver_value_use_new_branch}
\end{listing}
让我们看看当 match 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 x 中定义的值，所以代码继续执行。第二个匹配分支中的模式引入了一个新变量 y，它会匹配任何 Some 中的值。因为我们在 match 表达式的新作用域中，这是一个新变量，而不是开头声明为值 10 的那个 y。这个新的 y 绑定会匹配任何 Some 中的值，在这里是 x 中的值。因此这个 y 绑定了 x 中 Some 内部的值。这个值是 5，所以这个分支的表达式将会执行并打印出 Matched, y = 5。如果 x 的值是 None 而不是 Some(5)，头两个分支的模式不会匹配，所以会匹配下划线。这个分支的模式中没有引入变量 x，所以此时表达式中的 x 会是外部没有被覆盖的 x。在这个假想的例子中，match 将会打印 \bashinline{Default case, x = None}。一旦 match 表达式执行完毕，其作用域也就结束了，同理内部 y 的作用域也结束了。最后的 println! 会打印 \bashinline{at the end: x = Some(5), y = 10}。为了创建能够比较外部 x 和 y 的值，而不引入覆盖变量的 match 表达式，我们需要相应地使用带有条件的匹配守卫（match guard）。我们稍后将在 “匹配守卫提供的额外条件” 这一小节讨论匹配守卫。
\subsection{多个模式}
在 match 表达式中，可以使用 | 语法匹配多个模式，它代表 或（or）运算符模式。例如，如下代码将 x 的值与匹配分支相比较，第一个分支有 或 选项，意味着如果 x 的值匹配此分支的任一个值，它就会运行：
    \begin{rust}
    let x = 1;

    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("anything"),
    }

    \end{rust}
上面的代码会打印 one or two。
\subsection{通过 ..= 匹配值的范围}
\rustinline{..=} 语法允许你匹配一个闭区间范围内的值。在如下代码中，当模式匹配任何在给定范围内的值时，该分支会执行：
\begin{rust}
    let x = 5;

    match x {
        1..=5 => println!("one through five"),
        _ => println!("something else"),
    }
\end{rust}
如果 x 是 1、2、3、4 或 5，第一个分支就会匹配。这个语法在匹配多个值时相比使用 | 运算符来表达相同的意思更为方便；如果使用 | 则不得不指定 1 | 2 | 3 | 4 | 5。相反指定范围就简短的多，特别是在希望匹配比如从 1 到 1000 的数字的时候！编译器会在编译时检查范围不为空，而 char 和数字值是 Rust 仅有的可以判断范围是否为空的类型，所以范围只允许用于数字或 char 值。如下是一个使用 char 类型值范围的例子：
\begin{rust}
    let x = 'c';

    match x {
        'a'..='j' => println!("early ASCII letter"),
        'k'..='z' => println!("late ASCII letter"),
        _ => println!("something else"),
    }
\end{rust}
Rust 知道 'c' 位于第一个模式的范围内，并会打印出 early ASCII letter。
\subsection{解构并分解值}
也可以使用模式来解构结构体、枚举和元组，以便使用这些值的不同部分。让我们来分别看一看。
\subsubsection{解构结构体}
\coderef{code:unpack_struct_params} 展示带有两个字段 x 和 y 的结构体 Point，可以通过带有模式的 let 语句将其分解：
\begin{listing}[H]
    \centering
    \caption{解构一个结构体的字段为单独的变量}
    \begin{rust}
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}
    \end{rust}
    \label{code:unpack_struct_params}
\end{listing}
这段代码创建了变量 a 和 b 来匹配结构体 p 中的 x 和 y 字段。这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。因为变量名匹配字段名是常见的，同时因为 let Point { x: x, y: y } = p; 包含了很多重复，所以对于匹配结构体字段的模式存在简写：只需列出结构体字段的名称，则模式创建的变量会有相同的名称。\coderef{code:use_struct_to_unpack_struct_field} 展示了与\coderef{code:unpack_struct_params} 有着相同行为的代码，不过 let 模式创建的变量为 x 和 y 而不是 a 和 b：
\begin{listing}[H]
    \centering
    \caption{使用结构体字段简写来解构结构体字段}
    \begin{rust}
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}
    \end{rust}
    \label{code:use_struct_to_unpack_struct_field}
\end{listing}
这段代码创建了变量 x 和 y，与变量 p 中的 x 和 y 相匹配。其结果是变量 x 和 y 包含结构体 p 中的值。也可以使用字面值作为结构体模式的一部分进行解构，而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。\coderef{code:unpack_diff_type_enum} 展示了一个 match 语句将 Point 值分成了三种情况：直接位于 x 轴上（此时 y = 0 为真）、位于 y 轴上（x = 0）或不在任何轴上的点。
\begin{listing}[H]
    \centering
    \caption{解构和匹配模式中的字面值}
    \begin{rust}
fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {x}"),
        Point { x: 0, y } => println!("On the y axis at {y}"),
        Point { x, y } => {
            println!("On neither axis: ({x}, {y})");
        }
    }
}
    \end{rust}
    \label{code:match_struct_value}
\end{listing}
第一个分支通过指定字段 y 匹配字面值 0 来匹配任何位于 x 轴上的点。此模式仍然创建了变量 x 以便在分支的代码中使用。类似的，第二个分支通过指定字段 x 匹配字面值 0 来匹配任何位于 y 轴上的点，并为字段 y 创建了变量 y。第三个分支没有指定任何字面值，所以其会匹配任何其他的 Point 并为 x 和 y 两个字段创建变量。在这个例子中，值 p 因为其 x 包含 0 而匹配第二个分支，因此会打印出 On the y axis at 7。记住 match 表达式一旦找到一个匹配的模式就会停止检查其它分支，所以即使 Point { x: 0, y: 0} 在 x 轴上也在 y 轴上，这些代码也只会打印 On the x axis at 0。
\subsection{解构枚举}
本书之前曾经解构过枚举（例如第六章示例 6-5），不过当时没有明确提到解构枚举的模式需要对应枚举所定义的储存数据的方式。让我们以示例 6-2 中的 Message 枚举为例，编写一个 match 使用模式解构每一个内部值，如\coderef{code:unpack_diff_type_enum} 所示：
\begin{listing}[H]
    \centering
    \caption{解构包含不同类型值成员的枚举}
    \begin{rust}
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.");
        }
        Message::Move { x, y } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change the color to red {r}, green {g}, and blue {b}",)
        }
    }
}
    \end{rust}
    \label{code:unpack_diff_type_enum}
\end{listing}
这段代码会打印出 Change the color to red 0, green 160, and blue 255。尝试改变 msg 的值来观察其他分支代码的运行。对于像 Message::Quit 这样没有任何数据的枚举成员，不能进一步解构其值。只能匹配其字面值 Message::Quit，因此模式中没有任何变量。对于像 Message::Move 这样的类结构体枚举成员，可以采用类似于匹配结构体的模式。在成员名称后，使用大括号并列出字段变量以便将其分解以供此分支的代码使用。这里使用了\coderef{code:use_struct_to_unpack_struct_field} 所展示的简写。对于像 Message::Write 这样的包含一个元素，以及像 Message::ChangeColor 这样包含三个元素的类元组枚举成员，其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
\subsection{解构嵌套的结构体和枚举}
目前为止，所有的例子都只匹配了深度为一级的结构体或枚举，不过当然也可以匹配嵌套的项！例如，我们可以重构列表 18-15 的代码在 ChangeColor 消息中同时支持 RGB 和 HSV 色彩模式，如\coderef{code:match_mult_layer_enum} 所示：
\begin{listing}[H]
    \centering
    \caption{匹配嵌套的枚举}
    \begin{rust}
enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("Change color to red {r}, green {g}, and blue {b}");
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color to hue {h}, saturation {s}, value {v}")
        }
        _ => (),
    }
}
    \end{rust}
    \label{code:match_mult_layer_enum}
\end{listing}
match 表达式第一个分支的模式匹配一个包含 Color::Rgb 枚举成员的 Message::ChangeColor 枚举成员，然后模式绑定了 3 个内部的 i32 值。第二个分支的模式也匹配一个 Message::ChangeColor 枚举成员，但是其内部的枚举会匹配 Color::Hsv 枚举成员。我们可以在一个 match 表达式中指定这些复杂条件，即使会涉及到两个枚举。
\subsection{解构结构体和元组}
甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个复杂结构体的例子，其中结构体和元组嵌套在元组中，并将所有的原始类型解构出来：
\begin{rust}
    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
\end{rust}
这将复杂的类型分解成部分组件以便可以单独使用我们感兴趣的值。通过模式解构是一个方便利用部分值片段的手段，比如结构体中每个单独字段的值。
\subsection{忽略模式中的值}
有时忽略模式中的一些值是有用的，比如 match 中最后捕获全部情况的分支实际上没有做任何事，但是它确实对所有剩余情况负责。有一些简单的方法可以忽略模式中全部或部分值：使用 \_ 模式（我们已经见过了），在另一个模式中使用 \_ 模式，使用一个以下划线开始的名称，或者使用 .. 忽略所剩部分的值。让我们来分别探索如何以及为什么要这么做。
\subsubsection{使用 \_ 忽略整个值}
我们已经使用过下划线作为匹配但不绑定任何值的通配符模式了。虽然这作为 match 表达式最后的分支特别有用，也可以将其用于任意模式，包括函数参数中，如\coderef{code:use_underline_for_fn} 所示：
\begin{listing}[H]
    \centering
    \caption{在函数签名中使用 \_}
    \begin{rust}
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

fn main() {
    foo(3, 4);
}
    \end{rust}
    \label{code:use_underline_for_fn}
\end{listing}
这段代码会完全忽略作为第一个参数传递的值 3，并会打印出 This code only uses the y parameter: 4。大部分情况当你不再需要特定函数参数时，最好修改签名不再包含无用的参数。在一些情况下忽略函数参数会变得特别有用，比如实现 trait 时，当你需要特定类型签名但是函数实现并不需要某个参数时。这样可以避免一个存在未使用的函数参数的编译警告，就跟使用命名参数一样。
\subsection{使用嵌套的 \_ 忽略部分值}
也可以在一个模式内部使用\_ 忽略部分值，例如，当只需要测试部分值但在期望运行的代码中没有用到其他部分时。\coderef{code:use_underline_match_some} 展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置，但是可以取消设置，也可以在当前未设置时为其提供设置。
\begin{listing}[H]
    \centering
    \caption{当不需要 Some 中的值时在模式内使用下划线来匹配 Some 成员}
    \begin{rust}
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);

    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }

    println!("setting is {:?}", setting_value);
    \end{rust}
    \label{code:use_underline_match_some}
\end{listing}
这段代码会打印出 Can't overwrite an existing customized value 接着是 setting is Some(5)。在第一个匹配分支，我们不需要匹配或使用任一个 Some 成员中的值；重要的部分是需要测试 setting\_value 和 new\_setting\_value 都为 Some 成员的情况。在这种情况，我们打印出为何不改变 setting\_value，并且不会改变它。对于所有其他情况（setting\_value 或 new\_setting\_value 任一为 None），这由第二个分支的 \_ 模式体现，这时确实希望允许 new\_setting\_value 变为 setting\_value。也可以在一个模式中的多处使用下划线来忽略特定值，如\coderef{code:ignore_some_part} 所示，这里忽略了一个五元元组中的第二和第四个值：
\begin{listing}[H]
    \centering
    \caption{忽略元组的多个部分}
    \begin{rust}
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {first}, {third}, {fifth}")
        }
    }
    \end{rust}
    \label{code:ignore_some_part}
\end{listing}
这会打印出 Some numbers: 2, 8, 32，值 4 和 16 会被忽略。
\subsection{通过在名字前以一个 \_ 开头来忽略未使用的变量}
如果你创建了一个变量却不在任何地方使用它，Rust 通常会给你一个警告，因为未使用的变量可能会是个 bug。但是有时创建一个还未使用的变量是有用的，比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量，为此可以用下划线作为变量名的开头。\coderef{code:use_underline_for_var} 中创建了两个未使用变量，不过当编译代码时只会得到其中一个的警告：
\begin{listing}[H]
    \centering
    \caption{以下划线开始变量名以便去掉未使用变量警告}
    \begin{rust}
fn main() {
    let _x = 5;
    let y = 10;
}
    \end{rust}
    \label{code:use_underline_for_var}
\end{listing}
这里得到了警告说未使用变量 y，不过没有警告说使用 \_x。
\begin{note}
    只使用 \_ 和使用以下划线开头的名称有些微妙的不同：比如 \_x 仍会将值绑定到变量，而 \_ 则完全不会绑定。为了展示这个区别的意义，\coderef{code:use_underline_for_no_use_var} 会产生一个错误。
\end{note}
\begin{listing}[H]
    \centering
    \caption{以下划线开头的未使用变量仍然会绑定值，它可能会获取值的所有权}
    \begin{rust}
//这段代码无法通过编译！
    let s = Some(String::from("Hello!"));

    if let Some(_s) = s {
        println!("found a string");
    }

    println!("{:?}", s);
    \end{rust}
    \label{code:use_underline_for_no_use_var}
\end{listing}
我们会得到一个错误，因为 s 的值仍然会移动进 \_s，并阻止我们再次使用 s。然而只使用下划线本身，并不会绑定值。\coderef{code:single_underline_no_bind} 能够无错编译，因为 s 没有被移动进 \_：
\begin{listing}[H]
    \centering
    \caption{单独使用下划线不会绑定值}
    \begin{rust}
    let s = Some(String::from("Hello!"));

    if let Some(_) = s {
        println!("found a string");
    }

    println!("{:?}", s);
    \end{rust}
    \label{code:single_underline_no_bind}
\end{listing}
上面的代码能很好的运行；因为没有把 s 绑定到任何变量；它没有被移动。
\subsection{用 .. 忽略剩余值}
对于有多个部分的值，可以使用 .. 语法来只使用特定部分并忽略其它值，同时避免不得不每一个忽略值列出下划线。.. 模式会忽略模式中剩余的任何没有显式匹配的值部分。在\coderef{code:ignore_other_params} 中，有一个 Point 结构体存放了三维空间中的坐标。在 match 表达式中，我们希望只操作 x 坐标并忽略 y 和 z 字段的值：
\begin{listing}[H]
    \centering
    \caption{通过使用 .. 来忽略 Point 中除 x 以外的字段}
    \begin{rust}
    struct Point {
        x: i32,
        y: i32,
        z: i32,
    }

    let origin = Point { x: 0, y: 0, z: 0 };

    match origin {
        Point { x, .. } => println!("x is {}", x),
    }
    \end{rust}
    \label{code:ignore_other_params}
\end{listing}
这里列出了 x 值，接着仅仅包含了 .. 模式。这比不得不列出 y: \_ 和 z: \_ 要来得简单，特别是在处理有很多字段的结构体，但只涉及一到两个字段时的情形。.. 会扩展为所需要的值的数量。\coderef{code:unpack_some_part_for_tuple} 展示了元组中 .. 的应用：
\begin{listing}[H]
    \centering
    \caption{只匹配元组中的第一个和最后一个值并忽略掉所有其它值}
    \begin{rust}
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}
    \end{rust}
    \label{code:unpack_some_part_for_tuple}
\end{listing}
这里用 first 和 last 来匹配第一个和最后一个值。.. 将匹配并忽略中间的所有值。然而使用 .. 必须是无歧义的。如果期望匹配和忽略的值是不明确的，Rust 会报错。\coderef{code:try_to_ambi_use} 展示了一个带有歧义的 .. 例子，因此其不能编译：
\begin{listing}[H]
    \centering
    \caption{尝试以有歧义的方式运用 ..}
    \begin{rust}
//这段代码无法通过编译！
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {}", second)
        },
    }
}
    \end{rust}
    \label{code:try_to_ambi_use}
\end{listing}
如果编译上面的例子，会得到下面的错误：
\begin{rust}
$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
 --> src/main.rs:5:22
  |
5 |         (.., second, ..) => {
  |          --          ^^ can only be used once per tuple pattern
  |          |
  |          previously used here

error: could not compile `patterns` due to previous error
\end{rust}
Rust 不可能决定在元组中匹配 second 值之前应该忽略多少个值，以及在之后忽略多少个值。这段代码可能表明我们意在忽略 2，绑定 second 为 4，接着忽略 8、16 和 32；抑或是意在忽略 2 和 4，绑定 second 为 8，接着忽略 16 和 32，以此类推。变量名 second 对于 Rust 来说并没有任何特殊意义，所以会得到编译错误，因为在这两个地方使用 .. 是有歧义的。
\subsection{匹配守卫提供的额外条件}
\textbf{匹配守卫（match guard）}是一个指定于 match 分支模式之后的额外 if 条件，它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。这个条件可以使用模式中创建的变量。\coderef{code:use_match_g} 展示了一个 match，其中第一个分支有模式 Some(x) 还有匹配守卫 \rustinline{if x % 2 == 0} (当 x 是偶数的时候为真)：
\begin{listing}[H]
    \centering
    \caption{在模式中加入匹配守卫}
    \begin{rust}
    let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println!("The number {} is even", x),
        Some(x) => println!("The number {} is odd", x),
        None => (),
    }
    \end{rust}
    \label{code:use_match_g}
\end{listing}
上例会打印出 \bashinline{The number 4 is even}。当 num 与模式中第一个分支比较时，因为 Some(4) 匹配 Some(x) 所以可以匹配。接着匹配守卫检查 x 除以 2 的余数是否等于 0，因为它等于 0，所以第一个分支被选择。相反如果 num 为 Some(5)，因为 5 除以 2 的余数是 1 不等于 0 所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支，这次匹配因为它没有匹配守卫所以会匹配任何 Some 成员。无法在模式中表达类似 if x % 2 == 0 的条件，所以通过匹配守卫提供了表达类似逻辑的能力。这种替代表达方式的缺点是，编译器不会尝试为包含匹配守卫的模式检查穷尽性。在\coderef{code:conver_value_use_new_branch} 中，我们提到可以使用匹配守卫来解决模式中变量覆盖的问题，那里 match 表达式的模式中新建了一个变量而不是使用 match 之外的同名变量。新变量意味着不能够测试外部变量的值。\coderef{code:use_match_g_for_compare} 展示了如何使用匹配守卫修复这个问题。
\begin{listing}[H]
    \centering
    \caption{使用匹配守卫来测试与外部变量的相等性}
    \begin{rust}
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {n}"),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {y}", x);
}
    \end{rust}
    \label{code:use_match_g_for_compare}
\end{listing}
现在这会打印出 \textbf{Default case, x = Some(5)}。现在第二个匹配分支中的模式不会引入一个覆盖外部 y 的新变量 y，这意味着可以在匹配守卫中使用外部的 y。相比指定会覆盖外部 y 的模式 Some(y)，这里指定为 Some(n)。此新建的变量 n 并没有覆盖任何值，因为 match 外部没有变量 n。匹配守卫 if n == y 并不是一个模式所以没有引入新变量。这个 y 正是 外部的 y 而不是新的覆盖变量 y，这样就可以通过比较 n 和 y 来表达寻找一个与外部 y 相同的值的概念了。也可以在匹配守卫中使用 或 运算符 | 来指定多个模式，同时匹配守卫的条件会作用于所有的模式。\coderef{code:mult_pattern_for_g} 展示了结合匹配守卫与使用了 | 的模式的优先级。这个例子中重要的部分是匹配守卫 if y 作用于 4、5 和 6，即使这看起来好像 if y 只作用于 6：
\begin{listing}[H]
    \centering
    \caption{结合多个模式与匹配守卫}
    \begin{rust}
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
    \end{rust}
    \label{code:mult_pattern_for_g}
\end{listing}
这个匹配条件表明此分支值匹配 x 值为 4、5 或 6 同时 y 为 true 的情况。运行这段代码时会发生的是第一个分支的模式因 x 为 4 而匹配，不过匹配守卫 if y 为假，所以第一个分支不会被选择。代码移动到第二个分支，这会匹配，此程序会打印出 no。这是因为 if 条件作用于整个 4 | 5 | 6 模式，而不仅是最后的值 6。换句话说，匹配守卫与模式的优先级关系看起来像这样：
\begin{rust}
(4 | 5 | 6) if y => ...
\end{rust}
而不是：
\begin{rust}
4 | 5 | (6 if y) => ...
\end{rust}
可以通过运行代码时的情况看出这一点：如果匹配守卫只作用于由 | 运算符指定的值列表的最后一个值，这个分支就会匹配且程序会打印出 yes。
\subsection{\@ 绑定}
at 运算符（\rustinline{@}）允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。\coderef{code:use_at_for_bind_value} 展示了一个例子，这里我们希望测试 Message::Hello 的 id 字段是否位于 3..=7 范围内，同时也希望能将其值绑定到 id\_variable 变量中以便此分支相关联的代码可以使用它。可以将 id\_variable 命名为 id，与字段同名，不过出于示例的目的这里选择了不同的名称。
\begin{listing}[H]
    \centering
    \caption{使用 @ 在模式中绑定值的同时测试它}
    \begin{rust}
    enum Message {
        Hello { id: i32 },
    }

    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => println!("Found an id in range: {}", id_variable),
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {}", id),
    }
    \end{rust}
    \label{code:use_at_for_bind_value}
\end{listing}
上例会打印出 Found an id in range: 5。通过在 3..=7 之前指定 id\_variable @，我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。第二个分支只在模式中指定了一个范围，分支相关代码没有一个包含 id 字段实际值的变量。id 字段的值可以是 10、11 或 12，不过这个模式的代码并不知情也不能使用 id 字段中的值，因为没有将 id 值保存进一个变量。最后一个分支指定了一个没有范围的变量，此时确实拥有可以用于分支代码的变量 id，因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 id 字段的值进行测试：任何值都会匹配此分支。使用 @ 可以在一个模式中同时测试和保存变量值。
\subsection{总结}
模式是 Rust 中一个很有用的功能，它有助于我们区分不同类型的数据。当用于 match 语句时，Rust 确保模式会包含每一个可能的值，否则程序将不能编译。let 语句和函数参数的模式使得这些结构更强大，可以在将值解构为更小部分的同时为变量赋值。可以创建简单或复杂的模式来满足我们的要求。
